package com.example.spider.task.limit;

import com.example.spider.task.Task;
import com.example.spider.util.HtmlUtil;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import org.shoulder.core.util.JsonUtils;

import java.time.Duration;
import java.util.Map;

/**
 * 主要用于高限流，如 ip代理爬取：每几秒发一次请求，不管请求响应时间
 * todo 优化：避免惊群效应：设置定时器从队列里取【新问题：spider 排队时间浪费了，导致达不到实际限流？】
 *
 * @author lym
 */
@Data
@Accessors(chain = true)
public abstract class AbstractLimitStrategy implements LimitStrategy {

    /**
     * 网站名 、 分类，用于限流
     */
    protected String key;

    /**
     * 限流算法，爬虫中一律采用固定窗口，兼容性最好，无论目标服务器何种限流算法都可以支持。
     * 限流：两次请求间隔
     * 1. 若无指定proxy，则根据策略拿到proxy
     * 2. 过滤器过滤，如果这个 domain + 这个proxy 还处于冷却时间（限流中），则根据延迟时间，创建delayTask，为spiderTask指定proxy并重新放回任务队列，等待限流完毕后重新请求
     */
    protected Duration limitDuration;

    /**
     * 限流范围
     */
    protected LimitScope limitScope;

    public AbstractLimitStrategy(Duration limitDuration) {
        this.limitDuration = limitDuration;
        this.limitScope = LimitScope.DOMAIN_WITH_PROXY;
    }

    @JsonCreator
    public AbstractLimitStrategy(@JsonProperty("key") String key, @JsonProperty("limitDuration") Duration limitDuration,
                                 @JsonProperty("limitScope") LimitScope limitScope) {
        this.key = key;
        this.limitDuration = limitDuration;
        this.limitScope = limitScope;
    }

    public AbstractLimitStrategy(Map<String, Object> map) {
        // only for dejson
        this((String) map.get("key"),
                JsonUtils.parseObject((String) map.get("limitDuration"), Duration.class),
                LimitScope.valueOf((String) map.get("limitScope"))
        );
    }

    protected void checkProperties() {
        if (limitDuration.toMillis() < 1 || limitScope == null || (limitScope == LimitScope.CUSTOMER && key == null)) {
            throw new IllegalArgumentException("limitDuration must >= 1ms, key can't be null if scope=customer.");
        }
    }

    protected String genLimitKey(Task<?> task) {
        switch (limitScope) {
            case CUSTOMER:
                return key;
            //case SESSION:
            //return HtmlUtil.parseDomainFromUrl(task.getUrl()).replace('.', '_').replace('-', '_').replace(':', '_') + SESSIONID;
            case DOMAIN_WITH_PROXY:
            default:
                String domain = HtmlUtil.parseDomainFromUrl(task.getUrl()).replace('.', '_').replace('-', '_').replace(':', '_');
                String proxyId = task.getActuallyProxy().getIp() + "#" + task.getActuallyProxy().getPort();
                return domain + "#" + proxyId;
        }
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public Duration getLimitDuration() {
        return limitDuration;
    }

    public void setLimitDuration(Duration limitDuration) {
        this.limitDuration = limitDuration;
    }

    public void setLimitRate(LimitScope limitScope) {
        this.limitScope = limitScope;
    }
}
